This notebook outlines my process of tree based and Neural Network models. This notebook is dependent on the data table gameInfo generated from DataExtraction.RMD.

Packages

library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
-- Attaching packages --------------------------------------------------------------------------------- tidyverse 1.3.1 --
v ggplot2 3.3.5     v purrr   0.3.4
v tibble  3.1.2     v dplyr   1.0.7
v tidyr   1.1.3     v stringr 1.4.0
v readr   1.4.0     v forcats 0.5.1
-- Conflicts ------------------------------------------------------------------------------------ tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
Warning message:
In read_python_versions_from_registry("HCU", key = "PythonCore") :
  Unexpected format for PythonCore version: 3.10
library(data.table)
data.table 1.14.0 using 4 threads (see ?getDTthreads).  Latest news: r-datatable.com

Attaching package: ‘data.table’

The following objects are masked from ‘package:dplyr’:

    between, first, last

The following object is masked from ‘package:purrr’:

    transpose
library(randomForest)
Warning: package ‘randomForest’ was built under R version 4.1.2
randomForest 4.6-14
Type rfNews() to see new features/changes/bug fixes.

Attaching package: ‘randomForest’

The following object is masked from ‘package:dplyr’:

    combine

The following object is masked from ‘package:ggplot2’:

    margin
library(rpart.plot)
Warning: package ‘rpart.plot’ was built under R version 4.1.2
Loading required package: rpart
Warning: package ‘rpart’ was built under R version 4.1.2
library(word2vec)
Warning: package ‘word2vec’ was built under R version 4.1.2
library(Rtsne)
Warning: package ‘Rtsne’ was built under R version 4.1.2
library(plotly)
Warning: package ‘plotly’ was built under R version 4.1.2

Attaching package: ‘plotly’

The following object is masked from ‘package:ggplot2’:

    last_plot

The following object is masked from ‘package:stats’:

    filter

The following object is masked from ‘package:graphics’:

    layout
library(keras)
Warning: package ‘keras’ was built under R version 4.1.2
library(tfruns)
Warning: package ‘tfruns’ was built under R version 4.1.2
library(rsample)
Warning: package ‘rsample’ was built under R version 4.1.2

Loading Data from other part

load("../data/league.RDATA")
Warning message:
In read_python_versions_from_registry("HCU", key = "PythonCore") :
  Unexpected format for PythonCore version: 3.10

List to Store Results

data.tree <- list(
  models = list(),
  plots = list(),
  temp.data = list()
)
championCluster <- list(
  models = list(),
  plots = list(),
  temp.data = list()
)

Wrangling Data

So I want to make a basic tree classifier of projected winning team comps. For now, a basic model of simple champion tags will be used.

Setting up Training / Test Data

# Setting Seed for Reproducibility
set.seed(3)
data.tree$temp.data$sample <- sample(data.tree$temp.data$gameInfo.tree$match, nrow(data.tree$temp.data$gameInfo.tree)*.7)
data.tree$temp.data$train <- data.tree$temp.data$gameInfo.tree %>% 
  filter(match %in% data.tree$temp.data$sample)
data.tree$temp.data$test <- data.tree$temp.data$gameInfo.tree %>% 
  filter(!match %in% data.tree$temp.data$sample)

Generating Random Forest

set.seed(3)
data.tree$models$teamComp_forest <- randomForest(
  team_win ~ . - match,
  data = data.tree$temp.data$train,
  ntree = 500,
  importance = TRUE,
  na.action = na.omit
)

data.tree$models$teamComp_forest

Call:
 randomForest(formula = team_win ~ . - match, data = data.tree$temp.data$train,      ntree = 500, importance = TRUE, na.action = na.omit) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 3

        OOB estimate of  error rate: 50.08%
Confusion matrix:
      1     2 class.error
1 11396 12530   0.5236981
2 11377 12438   0.4777241
importance(data.tree$models$teamComp_forest)
                    1          2 MeanDecreaseAccuracy MeanDecreaseGini
Assassin_1  9.9540136  -6.814106            5.1561345         332.2679
Fighter_1  17.2205799 -13.858278            5.5311721         346.1510
Marksman_1  9.5225581  -8.116976            1.7123134         293.4874
Tank_1      7.4799838  -4.724412            3.9559451         285.2230
Mage_1     16.9019553 -14.924233            2.9464378         284.1115
Support_1  12.2424356 -11.952428            1.8623555         242.4686
Assassin_2  0.3371864  -2.088303           -2.3073435         359.3653
Fighter_2   2.4827423  -4.897604           -2.7091299         425.1212
Marksman_2  3.2506393  -4.942703           -1.7623174         369.9343
Tank_2      3.0022087  -5.144555           -2.4517398         338.7178
Mage_2      1.0833946  -1.633843           -0.6288478         389.8527
Support_2   0.6855808  -5.485852           -5.9403592         288.7111
varImpPlot(data.tree$models$teamComp_forest)

Let’s compare to a simple blue side always wins classifier:

data.tree$temp.data$gameInfo.tree %>% 
  count(team_win) %>% 
  mutate(n = n/sum(n))

Well, it’s slightly better than the naive blue side win classifier but clearly the number of champions with tags isn’t a very strong predictor of team success. With the current coding, I’m fairly certain that there won’t really be a robust classifier.

Let’s try to identify clusters of champion types. # Generating Input Team Sentences

Generating Model

Pretty clearly 5 main clusters of champions each corresponding to a role. Doesn’t really help too much in determining team compositions. I could set up a KNN to verify this but it seems pretty clear cut to me.

Neural Network

Wrangle Data

data.NN <- list()
data.NN$data.temp <- championCluster$temp.data$teams %>% 
  select(!match)
  
data.NN$data.temp

Running Model - See TeamCompNN.R

Hyperparameter Tuning

runs <- tuning_run(
  "TeamCompNN.R",
  flags = list(
    dropout = c(0.2, 0.3, 0.4, 0.5),
    unit = c(8, 16, 64)
  )
)

runs %>% 
  arrange(desc(metric_val_accuracy))
# So a dropout of .3 and 8 unit dense network seems to produce the best validation error
results
     loss  accuracy 
0.6919282 0.5214252 

Around 52% accuracy, not the best, but not bad considering the variance of league of legends.

Saving Model

save_model_tf(model, "initialNN.tf")
2021-12-21 18:54:50.959358: W tensorflow/python/util/util.cc:368] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.

Evaluating Example Team

model %>% predict("Sett Trundle Kindred Ziggs Leona")
          [,1]
[1,] 0.5169869

A very weird way to code a team comp predictor - I’ll try a different method in Part 3.

LS0tDQp0aXRsZTogIlRyZWVzIGFuZCBTdXBwb3J0IFZlY3RvciBNYWNoaW5lcyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNClRoaXMgbm90ZWJvb2sgb3V0bGluZXMgbXkgcHJvY2VzcyBvZiB0cmVlIGJhc2VkIGFuZCBOZXVyYWwgTmV0d29yayBtb2RlbHMuIFRoaXMgbm90ZWJvb2sgaXMgZGVwZW5kZW50IG9uIHRoZSBkYXRhIHRhYmxlIGdhbWVJbmZvIGdlbmVyYXRlZCBmcm9tIERhdGFFeHRyYWN0aW9uLlJNRC4NCg0KIyBQYWNrYWdlcw0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoZGF0YS50YWJsZSkNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KbGlicmFyeShycGFydC5wbG90KQ0KbGlicmFyeSh3b3JkMnZlYykNCmxpYnJhcnkoUnRzbmUpDQpsaWJyYXJ5KHBsb3RseSkNCmxpYnJhcnkoa2VyYXMpDQpsaWJyYXJ5KHRmcnVucykNCmxpYnJhcnkocnNhbXBsZSkNCmBgYA0KDQojIExvYWRpbmcgRGF0YSBmcm9tIG90aGVyIHBhcnQNCmBgYHtyfQ0KbG9hZCgiLi4vZGF0YS9sZWFndWUuUkRBVEEiKQ0KYGBgDQoNCiMgTGlzdCB0byBTdG9yZSBSZXN1bHRzDQpgYGB7cn0NCmRhdGEudHJlZSA8LSBsaXN0KA0KICBtb2RlbHMgPSBsaXN0KCksDQogIHBsb3RzID0gbGlzdCgpLA0KICB0ZW1wLmRhdGEgPSBsaXN0KCkNCikNCmNoYW1waW9uQ2x1c3RlciA8LSBsaXN0KA0KICBtb2RlbHMgPSBsaXN0KCksDQogIHBsb3RzID0gbGlzdCgpLA0KICB0ZW1wLmRhdGEgPSBsaXN0KCkNCikNCmBgYA0KDQoNCiMgV3JhbmdsaW5nIERhdGENClNvIEkgd2FudCB0byBtYWtlIGEgYmFzaWMgdHJlZSBjbGFzc2lmaWVyIG9mIHByb2plY3RlZCB3aW5uaW5nIHRlYW0gY29tcHMuIEZvciBub3csIGEgYmFzaWMgbW9kZWwgb2Ygc2ltcGxlIGNoYW1waW9uIHRhZ3Mgd2lsbCBiZSB1c2VkLg0KYGBge3J9DQpkYXRhLnRyZWUkdGVtcC5kYXRhJGdhbWVJbmZvLnRlbXAgPC0gZ2FtZUluZm8gJT4lIA0KICBsZWZ0X2pvaW4oDQogICAgY2hhbXBpb25zLnNjcmFwZWQsDQogICAgYnkgPSBjKCJjaGFtcGlvbk5hbWUiID0gIm5hbWUiKQ0KICApICU+JSANCiAgZ3JvdXBfYnkobWF0Y2gpICU+JSANCiAgbXV0YXRlKA0KICAgIHRlYW0gPSBybGVpZCh3aW4pDQogICkgJT4lIA0KICB1bmdyb3VwKCkNCg0KZGF0YS50cmVlJHRlbXAuZGF0YSRnYW1lSW5mby50YWdzIDwtIGRhdGEudHJlZSR0ZW1wLmRhdGEkZ2FtZUluZm8udGVtcCAlPiUgDQogIGdyb3VwX2J5KG1hdGNoLCB0ZWFtKSAlPiUgDQogIGNvdW50KHRhZykgJT4lIA0KICB1bmdyb3VwKCkgJT4lIA0KICBwaXZvdF93aWRlcigNCiAgICBuYW1lc19mcm9tID0gdGFnLA0KICAgIHZhbHVlc19mcm9tID0gbg0KICApICU+JSANCiAgcGl2b3Rfd2lkZXIoKSAlPiUgDQogIHJlcGxhY2UoaXMubmEoLiksIDApIA0KDQoNCmRhdGEudHJlZSR0ZW1wLmRhdGEkZ2FtZUluZm8udHJlZSA8LSBkYXRhLnRyZWUkdGVtcC5kYXRhJGdhbWVJbmZvLnRlbXAgJT4lIA0KICBmaWx0ZXIod2luID09IFRSVUUpICU+JSANCiAgc2VsZWN0KG1hdGNoLCB0ZWFtX3dpbiA9IHRlYW0pICU+JSANCiAgZGlzdGluY3QobWF0Y2gsIC5rZWVwX2FsbCA9IFQpICU+JSANCiAgbXV0YXRlKA0KICAgIHRlYW1fd2luID0gZmFjdG9yKHRlYW1fd2luLCBsZXZlbHMgPSBjKDEsIDIpKQ0KICApICU+JSANCiAgbGVmdF9qb2luKA0KICAgIGRhdGEudHJlZSR0ZW1wLmRhdGEkZ2FtZUluZm8udGFncyAlPiUgDQogICAgICBmaWx0ZXIodGVhbSA9PSAxKSAlPiUgDQogICAgICByZW5hbWVfd2l0aCgNCiAgICAgICAgLmZuID0gZnVuY3Rpb24oeCl7DQogICAgICAgICAgDQogICAgICAgICAgcGFzdGUwKHgsICJfMSIpICU+JSANCiAgICAgICAgICAgIHJldHVybigpDQogICAgICAgICAgDQogICAgICAgIH0sDQogICAgICAgIC5jb2xzID0gMzo4DQogICAgICApICU+JSANCiAgICAgIHNlbGVjdCghdGVhbSksDQogICAgYnkgPSAibWF0Y2giDQogICkgJT4lIA0KICBsZWZ0X2pvaW4oDQogICAgZGF0YS50cmVlJHRlbXAuZGF0YSRnYW1lSW5mby50YWdzICU+JSANCiAgICAgIGZpbHRlcih0ZWFtID09IDIpICU+JSANCiAgICAgIHJlbmFtZV93aXRoKA0KICAgICAgICAuZm4gPSBmdW5jdGlvbih4KXsNCiAgICAgICAgICANCiAgICAgICAgICBwYXN0ZTAoeCwgIl8yIikgJT4lIA0KICAgICAgICAgICAgcmV0dXJuKCkNCiAgICAgICAgICANCiAgICAgICAgfSwNCiAgICAgICAgLmNvbHMgPSAzOjgNCiAgICAgICkgJT4lIA0KICAgICAgc2VsZWN0KCF0ZWFtKSwNCiAgICBieSA9ICJtYXRjaCINCiAgKSAlPiUgDQogIG11dGF0ZV9pZihpcy5pbnRlZ2VyLCBhcy5mYWN0b3IpDQoNCmRhdGEudHJlZSR0ZW1wLmRhdGEkZ2FtZUluZm8udHJlZQ0KYGBgDQoNCiMgU2V0dGluZyB1cCBUcmFpbmluZyAvIFRlc3QgRGF0YQ0KYGBge3J9DQojIFNldHRpbmcgU2VlZCBmb3IgUmVwcm9kdWNpYmlsaXR5DQpzZXQuc2VlZCgzKQ0KIyBOZXh0IHRpbWUgdXNlIHJzYW1wbGUgDQpkYXRhLnRyZWUkdGVtcC5kYXRhJHNhbXBsZSA8LSBzYW1wbGUoZGF0YS50cmVlJHRlbXAuZGF0YSRnYW1lSW5mby50cmVlJG1hdGNoLCBucm93KGRhdGEudHJlZSR0ZW1wLmRhdGEkZ2FtZUluZm8udHJlZSkqLjcpDQpkYXRhLnRyZWUkdGVtcC5kYXRhJHRyYWluIDwtIGRhdGEudHJlZSR0ZW1wLmRhdGEkZ2FtZUluZm8udHJlZSAlPiUgDQogIGZpbHRlcihtYXRjaCAlaW4lIGRhdGEudHJlZSR0ZW1wLmRhdGEkc2FtcGxlKQ0KZGF0YS50cmVlJHRlbXAuZGF0YSR0ZXN0IDwtIGRhdGEudHJlZSR0ZW1wLmRhdGEkZ2FtZUluZm8udHJlZSAlPiUgDQogIGZpbHRlcighbWF0Y2ggJWluJSBkYXRhLnRyZWUkdGVtcC5kYXRhJHNhbXBsZSkNCmBgYA0KDQojIEdlbmVyYXRpbmcgUmFuZG9tIEZvcmVzdA0KYGBge3J9DQpzZXQuc2VlZCgzKQ0KZGF0YS50cmVlJG1vZGVscyR0ZWFtQ29tcF9mb3Jlc3QgPC0gcmFuZG9tRm9yZXN0KA0KICB0ZWFtX3dpbiB+IC4gLSBtYXRjaCwNCiAgZGF0YSA9IGRhdGEudHJlZSR0ZW1wLmRhdGEkdHJhaW4sDQogIG50cmVlID0gNTAwLA0KICBpbXBvcnRhbmNlID0gVFJVRSwNCiAgbmEuYWN0aW9uID0gbmEub21pdA0KKQ0KDQpkYXRhLnRyZWUkbW9kZWxzJHRlYW1Db21wX2ZvcmVzdA0KYGBgDQpgYGB7cn0NCmltcG9ydGFuY2UoZGF0YS50cmVlJG1vZGVscyR0ZWFtQ29tcF9mb3Jlc3QpDQp2YXJJbXBQbG90KGRhdGEudHJlZSRtb2RlbHMkdGVhbUNvbXBfZm9yZXN0KQ0KYGBgDQpMZXQncyBjb21wYXJlIHRvIGEgc2ltcGxlIGJsdWUgc2lkZSBhbHdheXMgd2lucyBjbGFzc2lmaWVyOg0KYGBge3J9DQpkYXRhLnRyZWUkdGVtcC5kYXRhJGdhbWVJbmZvLnRyZWUgJT4lIA0KICBjb3VudCh0ZWFtX3dpbikgJT4lIA0KICBtdXRhdGUobiA9IG4vc3VtKG4pKQ0KYGBgDQpXZWxsLCBpdCdzIHNsaWdodGx5IGJldHRlciB0aGFuIHRoZSBuYWl2ZSBibHVlIHNpZGUgd2luIGNsYXNzaWZpZXIgYnV0IGNsZWFybHkgdGhlIG51bWJlciBvZiBjaGFtcGlvbnMgd2l0aCB0YWdzIGlzbid0IGEgdmVyeSBzdHJvbmcgcHJlZGljdG9yIG9mIHRlYW0gc3VjY2Vzcy4gV2l0aCB0aGUgY3VycmVudCBjb2RpbmcsIEknbSBmYWlybHkgY2VydGFpbiB0aGF0IHRoZXJlIHdvbid0IHJlYWxseSBiZSBhIHJvYnVzdCBjbGFzc2lmaWVyLg0KDQpMZXQncyB0cnkgdG8gaWRlbnRpZnkgY2x1c3RlcnMgb2YgY2hhbXBpb24gdHlwZXMuDQojIEdlbmVyYXRpbmcgSW5wdXQgVGVhbSBTZW50ZW5jZXMgDQpgYGB7cn0NCmNoYW1waW9uQ2x1c3RlciR0ZW1wLmRhdGEkdGVhbXMgPC0gZ2FtZUluZm8gJT4lIA0KICBzZWxlY3QobWF0Y2gsIHdpbiwgY2hhbXBpb25OYW1lKSAlPiUgDQogIGdyb3VwX2J5KG1hdGNoLCB3aW4pICU+JSANCiAgbXV0YXRlKGNoYW1waW9uTnVtYmVyID0gcm93X251bWJlcigpKSAlPiUgDQogIHBpdm90X3dpZGVyKA0KICAgIG5hbWVzX2Zyb20gPSBjaGFtcGlvbk51bWJlciwNCiAgICB2YWx1ZXNfZnJvbSA9IGNoYW1waW9uTmFtZQ0KICApICU+JSANCiAgdHJhbnNtdXRlKG1hdGNoID0gbWF0Y2gsIHdpbiA9IHdpbiwgdGVhbSA9IHN0cl9jKGAxYCxgMmAsYDNgLGA0YCxgNWAsIHNlcCA9ICIgIikpICU+JSANCiAgdW5ncm91cCgpIA0KDQpjaGFtcGlvbkNsdXN0ZXIkdGVtcC5kYXRhJHRlYW1zDQp3cml0ZV9jc3YoY2hhbXBpb25DbHVzdGVyJHRlbXAuZGF0YSR0ZWFtcywgIi4uL2RhdGEvdGVhbU5hbWVzLmNzdiIpDQpgYGANCiMgR2VuZXJhdGluZyBNb2RlbA0KYGBge3J9DQpzZXQuc2VlZCgzKQ0KY2hhbXBpb25DbHVzdGVyJG1vZGVscyRubHBNb2RlbCA8LSB3b3JkMnZlYygNCiAgeCA9IGNoYW1waW9uQ2x1c3RlciR0ZW1wLmRhdGEkdGVhbXMkdGVhbSwgDQogIHR5cGUgPSAic2tpcC1ncmFtIiwgDQogIGRpbSA9IDIwLCANCiAgaXRlciA9IDE1DQopDQoNCiMgRW1iZWRkaW5nIE1hdHJpeA0KY2hhbXBpb25DbHVzdGVyJG1vZGVscyRlbWJlZGRpbmdNYXRyaXggPC0gYXMubWF0cml4KGNoYW1waW9uQ2x1c3RlciRtb2RlbHMkbmxwTW9kZWwpDQoNCiMgQXBwbHlpbmcgVFNuZSANCmNoYW1waW9uQ2x1c3RlciRtb2RlbHMkVHNuZSA8LSBSdHNuZShjaGFtcGlvbkNsdXN0ZXIkbW9kZWxzJGVtYmVkZGluZ01hdHJpeCwgcGNhID0gRkFMU0UpDQoNCmNoYW1waW9uQ2x1c3RlciRwbG90cyRtYXAgPC0gY2hhbXBpb25DbHVzdGVyJG1vZGVscyRUc25lJFkgJT4lIA0KICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogIG11dGF0ZShjaGFtcGlvbiA9IHJvdy5uYW1lcyhjaGFtcGlvbkNsdXN0ZXIkbW9kZWxzJGVtYmVkZGluZ01hdHJpeCkpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBWMSwgeSA9IFYyLCBsYWJlbCA9IGNoYW1waW9uKSkgKyANCiAgZ2VvbV9wb2ludCgpIA0KDQpjaGFtcGlvbkNsdXN0ZXIkcGxvdHMkbWFwIDwtIGNoYW1waW9uQ2x1c3RlciRwbG90cyRtYXAgJT4lIA0KICBnZ3Bsb3RseSgpDQoNCmNoYW1waW9uQ2x1c3RlciRwbG90cyRtYXAgDQpgYGANClByZXR0eSBjbGVhcmx5IDUgbWFpbiBjbHVzdGVycyBvZiBjaGFtcGlvbnMgZWFjaCBjb3JyZXNwb25kaW5nIHRvIGEgcm9sZS4gRG9lc24ndCByZWFsbHkgaGVscCB0b28gbXVjaCBpbiBkZXRlcm1pbmluZyB0ZWFtIGNvbXBvc2l0aW9ucy4gSSBjb3VsZCBzZXQgdXAgYSBLTk4gdG8gdmVyaWZ5IHRoaXMgYnV0IGl0IHNlZW1zIHByZXR0eSBjbGVhciBjdXQgdG8gbWUuDQoNCiMgTmV1cmFsIE5ldHdvcmsNCiMjIFdyYW5nbGUgRGF0YQ0KYGBge3J9DQpkYXRhLk5OIDwtIGxpc3QoKQ0KZGF0YS5OTiRkYXRhLnRlbXAgPC0gY2hhbXBpb25DbHVzdGVyJHRlbXAuZGF0YSR0ZWFtcyAlPiUgDQogIHNlbGVjdCghbWF0Y2gpDQogIA0KZGF0YS5OTiRkYXRhLnRlbXANCmBgYA0KDQojIFJ1bm5pbmcgTW9kZWwgLSBTZWUgVGVhbUNvbXBOTi5SDQojIyBIeXBlcnBhcmFtZXRlciBUdW5pbmcNCmBgYHtyfQ0KcnVucyA8LSB0dW5pbmdfcnVuKA0KICAiVGVhbUNvbXBOTi5SIiwNCiAgZmxhZ3MgPSBsaXN0KA0KICAgIGRyb3BvdXQgPSBjKDAuMiwgMC4zLCAwLjQsIDAuNSksDQogICAgdW5pdCA9IGMoOCwgMTYsIDY0KQ0KICApDQopDQoNCnJ1bnMgJT4lIA0KICBhcnJhbmdlKGRlc2MobWV0cmljX3ZhbF9hY2N1cmFjeSkpDQojIFNvIGEgZHJvcG91dCBvZiAuMyBhbmQgOCB1bml0IGRlbnNlIG5ldHdvcmsgc2VlbXMgdG8gcHJvZHVjZSB0aGUgYmVzdCB2YWxpZGF0aW9uIGVycm9yDQpgYGANCg0KYGBge3J9DQpyZXN1bHRzDQpgYGANCkFyb3VuZCA1MiUgYWNjdXJhY3ksIG5vdCB0aGUgYmVzdCwgYnV0IG5vdCBiYWQgY29uc2lkZXJpbmcgdGhlIHZhcmlhbmNlIG9mIGxlYWd1ZSBvZiBsZWdlbmRzLg0KDQojIFNhdmluZyBNb2RlbA0KYGBge3J9DQpzYXZlX21vZGVsX3RmKG1vZGVsLCAiaW5pdGlhbE5OLnRmIikNCmBgYA0KYGBge3IgaW5jbHVkZSA9IEZ9DQptb2RlbCA8LSBsb2FkX21vZGVsX3RmKCIuL2luaXRpYWxOTi50ZiIpDQpgYGANCg0KDQojIEV2YWx1YXRpbmcgRXhhbXBsZSBUZWFtDQpgYGB7cn0NCm1vZGVsICU+JSBwcmVkaWN0KCJTZXR0IFRydW5kbGUgS2luZHJlZCBaaWdncyBMZW9uYSIpDQpgYGANCkEgdmVyeSB3ZWlyZCB3YXkgdG8gY29kZSBhIHRlYW0gY29tcCBwcmVkaWN0b3IgLSBJJ2xsIHRyeSBhIGRpZmZlcmVudCBtZXRob2QgaW4gUGFydCAzLg0K